1時間プログラミング 006 jqのフィルタリング結果を確認するTUIツール
#1時間プログラミング
概要
jqコマンドのフィルタリング結果を確認するTUIツール
所要時間: 5時間
1時間で作れるわけないだろ!
使い方
code:terminal
$ cat sample/foo.json | cargo run
https://raw.githubusercontent.com/emanon001/1hour/main/006-jq-finder/jq-finder1.png
ユーザーが入力したフィルタを適用した結果が、出力欄に反映される
https://raw.githubusercontent.com/emanon001/1hour/main/006-jq-finder/jq-finder2.png
実装
キー入力を受け取り、都度jqコマンドを呼び出すだけ
Rustで実装
TUIフレームワークはtui-rsを使用
公式ドキュメントとサンプル実装を見て、それっぽく作った
入力欄と出力欄のみのシンプルな構成
code:ui.rs
pub fn ui<B: Backend>(f: &mut Frame<B>, app: &State) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(Constraint::Length(3), Constraint::Min(0).as_ref())
.split(f.size());
let filter = Paragraph::new(app.filter.as_ref())
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Filter"));
f.render_widget(filter, chunks0);
f.set_cursor(
// Put cursor past the end of the input text
chunks0.x + app.filter.width() as u16 + 1,
// Move one line down, from the border to the input line
chunks0.y + 1,
);
let output = Paragraph::new(app.output.as_ref())
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Output"));
f.render_widget(output, chunks1);
}
スクロールの実装は面倒なので対応していない
jqコマンドの呼び出しはstd::process:Commandを使用
code:jq.rs
impl Jq {
pub fn new(path: &str, json: &str) -> Self {
Self {
path: path.to_string(),
json: json.to_string(),
}
}
pub fn execute(&self, filter: &str) -> Result<Output> {
let mut jq = Command::new(&self.path)
.arg(filter)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let jq_stdin = jq.stdin.as_mut().unwrap();
jq_stdin.write_all(self.json.as_bytes())?;
let output = jq.wait_with_output()?;
Ok(output)
}
}
キー入力後すぐにjqを呼び出すのではなく、遅延を入れるようにした
code:rs
// update last input datetime
let input_at = Instant::now();
{
let mut last_input_at = last_input_at.lock().unwrap();
*last_input_at = input_at;
}
// filter json
let state = Arc::clone(&state);
let jq = Arc::clone(&jq);
let last_input_at = Arc::clone(&last_input_at);
let _ = thread::spawn(move || {
// 遅延を入れる。待っている最中にキー入力があった場合は処理を打ち切る
thread::sleep(Duration::from_millis(500));
let last_input_at = last_input_at.lock().unwrap();
if input_at != *last_input_at {
return;
}
let mut state = state.lock().unwrap();
if let Ok(output) = jq.execute(&state.filter) {
let _ = state.update_output(output);
}
});
cat foo.json | cargo run のようにパイプで繋ぎたいのだが、キーボードからの入力もstdinから受け取る必要があるため、中々上手くいかなかった
crossterm::macros::excute!()内でエラーが発生する
と大体同じ問題
macOSでのみ発生するとある。悲しい
で修正パッチが作成されている
まだ本家にはマージされていないが、使う場合はCargo.tomlに以下のコードを追加する
code:Cargo.toml
patch.crates-io
crossterm = { git = "https://github.com/yyogo/crossterm.git", branch = "use-select-in-unix" }
ref.
雑な回避策としては、パイプ経由でJSONを受け取った後にtmpファイルとして書き出し、そのパスを指定した子プロセスでTUIツールを実行するとか
感想
1時間を大幅に越えた。調査時間を含めて5時間くらい?
TUIフレームワークを初めて使用した。面白いので、他のお題でも使ってみようと思う